In [3]:
%matplotlib inline

In [4]:
# Write your imports here
import sympy
import numpy as np
import matplotlib.pyplot as plt
import math

High-School Maths Exercise

Getting to Know Jupyter Notebook. Python Libraries and Best Practices. Basic Workflow

Problem 1. Markdown

Jupyter Notebook is a very light, beautiful and convenient way to organize your research and display your results. Let's play with it for a while.

First, you can double-click each cell and edit its content. If you want to run a cell (that is, execute the code inside it), use Cell > Run Cells in the top menu or press Ctrl + Enter.

Second, each cell has a type. There are two main types: Markdown (which is for any kind of free text, explanations, formulas, results... you get the idea), and code (which is, well... for code :D).

Let me give you a...

Quick Introduction to Markdown

Text and Paragraphs

There are several things that you can do. As you already saw, you can write paragraph text just by typing it. In order to create a new paragraph, just leave a blank line. See how this works below:

This is some text.
This text is on a new line, but it will continue the same paragraph (so you can make your paragraphs more easily readable by just continuing on a new line, or just go on and on like this one line is ever continuing).

This text is displayed in a new paragraph.

And this is yet another paragraph.

Result:

This is some text. This text is on a new line, but it will continue the same paragraph (so you can make your paragraphs more easily readable by just continuing on a new line, or just go on and on like this one line is ever continuing).

This text is displayed in a new paragraph.

And this is yet another paragraph.

Headings

There are six levels of headings. Level one is the highest (largest and most important), and level 6 is the smallest. You can create headings of several types by prefixing the header line with one to six "#" symbols (this is called a pound sign if you are ancient, or a sharp sign if you're a musician... or a hashtag if you're too young :D). Have a look:

# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6

Result:

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

It is recommended that you have only one H1 heading - this should be the header of your notebook (or scientific paper). Below that, you can add your name or just jump to the explanations directly.

Emphasis

You can create emphasized (stonger) text by using a bold or italic font. You can do this in several ways (using asterisks (*) or underscores (_)). In order to "escape" a symbol, prefix it with a backslash (). You can also strike thorugh your text in order to signify a correction.

**bold** __bold__
*italic* _italic_

This is \*\*not \*\* bold.

I ~~didn't make~~ a mistake.

Result:

bold bold italic italic

This is **not** bold.

I didn't make a mistake.

Lists

You can add two types of lists: ordered and unordered. Lists can also be nested inside one another. To do this, press Tab once (it will be converted to 4 spaces).

To create an ordered list, just type the numbers. Don't worry if your numbers are wrong - Jupyter Notebook will create them properly for you. Well, it's better to have them properly numbered anyway...

1. This is
2. A list
10. With many
9. Items
    1. Some of which
    2. Can
        3. Be nested
42. You can also
    * Mix 
    * list
    * types

Result:

  1. This is
  2. A list
  3. With many
  4. Items
    1. Some of which
    2. Can
      1. Be nested
  5. You can also
    • Mix
    • list
    • types

To create an unordered list, type an asterisk, plus or minus at the beginning:

* This is
* An
    + Unordered
    - list

Result:

  • This is
  • An
    • Unordered
      • list

There are many ways to create links but we mostly use one of them: we present links with some explanatory text. See how it works:

This is [a link](http://google.com) to Google.

Result:

This is a link to Google.

Images

They are very similar to links. Just prefix the image with an exclamation mark. The alt(ernative) text will be displayed if the image is not available. Have a look (hover over the image to see the title text):

![Alt text](http://i.imgur.com/dkY1gph.jpg "Do you know that "taco cat" is a palindrome? Thanks to The Oatmeal :)")

Result:

If you want to resize images or do some more advanced stuff, just use HTML.

Did I mention these cells support HTML, CSS and JavaScript? Now I did.

Tables

These are a pain because they need to be formatted (somewhat) properly. Here's a good table generator. Just select File > Paste table data... and provide a tab-separated list of values. It will generate a good-looking ASCII-art table for you.

| Cell1 | Cell2 | Cell3 |
|-------|-------|-------|
| 1.1   | 1.2   | 1.3   |
| 2.1   | 2.2   | 2.3   |
| 3.1   | 3.2   | 3.3   |

Result:

Cell1 Cell2 Cell3
1.1 1.2 1.3
2.1 2.2 2.3
3.1 3.2 3.3
Code

Just use triple backtick symbols. If you provide a language, it will be syntax-highlighted. You can also use inline code with single backticks.

```python
def square(x):
    return x ** 2
```
This is `inline` code. No syntax highlighting here.

Result:

def square(x):
    return x ** 2

This is inline code. No syntax highlighting here.

Now it's your turn to have some Markdown fun. In the next cell, try out some of the commands. You can just throw in some things, or do something more structured (like a small notebook).

Write some Markdown here.

Problem 1 solution

just few test of markdown

lets try some sorted numbered list

  1. this is the first point
    1. this is inclined point
    2. this is sedonc inclined point
  2. This is second main point

Now lets try unsorted list

  • this is a point
    • and this is inclined point
    • second indlined point
  • Again some point
    • this will have sublist
      • this is sublist
      • of few elements
      • handing here

One table

SoftUni Google
1. and if you click this
2. it will take you
3. to SoftUni or Google

Paragraph

Heading 1

Problem 2. Formulas and LaTeX. Quadratic Equations

Writing math formulas has always been hard. But scientists don't like difficulties and prefer standards. So, thanks to Donald Knuth (a very popular computer scientist, who also invented a lot of algorithms), we have a nice typesetting system, called LaTeX (pronounced lah-tek). We'll be using it mostly for math formulas, but it has a lot of other things to offer.

Let's flex our math muscles a bit and try to write some formulas at the same time.

There are two main ways to write formulas. You could enclose them in single $ signs like this: $ ax + b $, which will create an inline formula: $ ax + b $. You can also enclose them in double $ signs $$ ax + b $$ to produce $$ ax + b $$.

Most commands start with a backslash and accept parameters either in square brackets [] or in curly braces {}. For example, to make a fraction, you typically would write $$ \frac{a}{b} $$: $$ \frac{a}{b} $$.

Here's a resource where you can look up the basics of the math syntax. You can also search StackOverflow - there are all sorts of solutions there.

Now let's have some fun with quadratic equations. Remember that a quadratic equation has the general form $$ ax^2 + bx + c = 0,\ a \neq 0 $$

We know what the solutions are. But can we derive them? Of course, we can. We need to express $x$ in terms of the parameters $a, b, c$. There are several ways to do this. I'm gong to use a little different approach than most people do because it's more intuitive and leads to the right answer without too much "brilliant insight" and "WTF did I just do?".

Note: If you get stuck at some point, you can look up a solution on the Internet.

We start with the equation $$ ax^2 + bx + c = 0 $$

We can try to get something like the sum of squares formula. Recall that $$ (px+q)^2 = p^2x^2 + 2pqx + q^2 $$

Let's take the first two terms ($ax^2 + bx$). We can see that they fit the formula above almost perfectly. We have $$ a = p^2, b = 2pq $$ $$ \Rightarrow p = \sqrt{a}, q = \frac{b}{2p} = \frac{b}{2\sqrt{a}}$$.

We also need to add $q^2=\frac{b^2}{4a}$. Since this is an equation, we have to add it to both sides of the original equation. We get: $$ p^2x^2 + 2pqx + q^2 + c = q^2$$

Now, express the equation above in terms of $a, b, c$: $$ {\sqrt{a}}^2x^2 + 2\sqrt{a}\frac{b}{2\sqrt{a}}x + \frac{b^2}{4a} + c = \frac{b^2}{4a} $$

Place $c$ on the right-hand side. We now have our squared formula on the left: $$ \sqrt{a}^2x^2 + 2\sqrt{a}\frac{b}{2\sqrt{a}}x + \frac{b^2}{4a} = \frac{b^2}{4a} - c $$ $$ \left(\sqrt{a}\right)^2x^2 + 2\left(\sqrt{a}\right)\left(\frac{b}{2\sqrt{a}}\right)x + \left(\frac{b}{2\sqrt{a}}\right)^2 = \frac{b^2}{4a} - c $$ $$ \left(\sqrt{a}x + \frac{b}{2\sqrt{a}}\right)^2 = \frac{b^2}{4a} - c $$

Take the square root of both sides. Note that this means just removing the second power on the left-hand side (because the number is positive) but the right-haand side can be positive or negative: $\pm$: $$ \sqrt{a}x + \frac{b}{2\sqrt{a}} = \pm\sqrt{\frac{b^2}{4a} - \frac{4ac}{4a}} $$ $$ \sqrt{a}x + \frac{b}{2\sqrt{a}} = \pm\sqrt{\frac{b^2 - 4ac}{4a}} $$

You should get something like $\alpha x + \beta = \pm\sqrt{\frac{\gamma}{\delta}}$, where $\alpha, \beta, \gamma, \delta$ are all expresions. Now there's only one term containing $x$. Leave it to the left and transfer everything else to the right: $$ \sqrt{a}x = \pm\sqrt{\frac{b^2 - 4ac}{4a}} - \frac{b}{2\sqrt{a}}$$

To get $x$, divide both sides of the equation by the coefficient $\alpha$. Simplify the expression: $$ x = \left(\frac{\pm\sqrt{b^2 - 4ac}}{2\sqrt{a}} - \frac{b}{2\sqrt{a}}\right)\frac{1}{\sqrt{a}} \quad \Rightarrow \quad x = \frac{\pm\sqrt{b^2 - 4ac} - b}{2a} $$

If everything went OK, you should have got the familar expression for the roots of the quadratic equation: $$ x = \frac{-b \pm\sqrt{b^2 - 4ac}}{2a} $$

Let's play around some more. Remember Vieta's formulas? Let's very quickly calculate them.

Express the sum and product of roots in terms of $a, b, c$. Substitute $x_1$ and $x_2$ for the two roots we just got. Simplify the result and you'll get that :)

Write your result here.

$$ x_1 + x_2 = \frac{-b + \sqrt{b^2 - 4ac}}{2a} + \frac{-b - \sqrt{b^2 - 4ac}}{2a} $$ $$ x_1 + x_2 = \frac{-b + \sqrt{b^2 - 4ac} -b - \sqrt{b^2 - 4ac}}{2a} = \frac{-2b}{2a}$$ $$ x_1 + x_2 = -\frac{b}{a} $$ $$ x_1x_2= \frac{-b + \sqrt{b^2 - 4ac}}{2a}\cdot\frac{-b - \sqrt{b^2 - 4ac}}{2a} = \frac{\left(-b\right)^2 + b\sqrt{b^2 - 4ac} - b\sqrt{b^2 - 4ac} - \left(\sqrt{b^2 - 4ac}\right)^2}{4a^2} = \frac{b^2 - b^2 + 4ac}{4a^2} $$ $$ x_1x_2= \frac{c}{a} $$ If you worked correctly, you should have got the formulas $$x_1 + x_2 = -\frac{b}{a}, x_1x_2 = \frac{c}{a}$$ Now let's do something else. Let's **factor** the quadratic equation. This means, we'll just rearrange the terms so that they're more useful. Start again with the basic equation: $$ ax^2 + bx + c = 0 $$ Divide both sides of the equation by $a$:

Write your result here.

$$ x^2 + \frac{b}{a}x + \frac{c}{a} = 0 $$

Now you get $b/a$ and $c/a$. Replace them with the sum and product of roots. Be very careful about the signs!

Write your result here.

$$ x^2 -\left(x_1 + x_2\right)x + x_1x_2 = 0 $$

You should have some braces. Expand them:

Write your result here.

$$ xx - xx_1 - xx_2 + x_1x_2 = 0 $$$$ \left(x - x_1\right)\left(x - x_2\right) = 0 $$

You should now get an expression containing $x$ (our variable) and $x_1, x_2$ (the roots). Please bear in mind those are different.

Find a way to group them and rearrange the symbols a bit. If you do this, you can arrive at the expression $$ (x - x_1)(x - x_2) = 0 $$

AHA! How is this formula useful? We can now "generate" a quadratic function by only knowing the roots. For example, generate a quadratic function which has roots -1 and 3. Write it in the form $ ax^2 + bx + c = 0 $:

Write your result here.

$$ \left(x - \left(-1\right)\right)\left(x - 3\right) = 0 $$$$ x^2 - 3x + x -3 = 0 $$$$ x^2 - 2x - 3 = 0 $$

Problem 3. Solving with Python

Let's first do some symbolic computation. We need to import sympy first.

Should your imports be in a single cell at the top or should they appear as they are used? There's not a single valid best practice. Most people seem to prefer imports at the top of the file though. Note: If you write new code in a cell, you have to re-execute it!

Let's use sympy to give us a quick symbolic solution to our equation. First import sympy (you can use the second cell in this notebook):

import sympy

Next, create symbols for all variables and parameters. You may prefer to do this in one pass or separately:

x = sympy.symbols('x')
a, b, c = sympy.symbols('a b c')

Now solve:

sympy.solve(a * x**2 + b * x + c)

Hmmmm... we didn't expect that :(. We got an expression for $a$ because the library tried to solve for the first symbol it saw. This is an equation and we have to solve for $x$. We can provide it as a second paramter:

sympy.solve(a * x**2 + b * x + c, x)

Finally, if we use sympy.init_printing(), we'll get a LaTeX-formatted result instead of a typed one. This is very useful because it produces better-looking formulas.


In [5]:
# Write your code here
# import sympy
x = sympy.symbols('x') # define sybols for parameters
a, b, c = sympy.symbols('a b c') # define simbols for parameters
sympy.init_printing() # LaTeX-formatted result for printing
sympy.solve(a * x**2 + b * x + c, x) # solve parametric equation


Out[5]:
$$\left [ \frac{1}{2 a} \left(- b + \sqrt{- 4 a c + b^{2}}\right), \quad - \frac{1}{2 a} \left(b + \sqrt{- 4 a c + b^{2}}\right)\right ]$$

How about a function that takes $a, b, c$ (assume they are real numbers, you don't need to do additional checks on them) and returns the real roots of the quadratic equation?

Remember that in order to calculate the roots, we first need to see whether the expression under the square root sign is non-negative.

If $\sqrt{b^2 - 4ac} > 0$, the equation has two real roots: $x_1, x_2$

If $\sqrt{b^2 - 4ac} = 0$, the equation has one real root: $x_1 = x_2$

If $\sqrt{b^2 - 4ac} < 0$, the equation has zero real roots

Write a function which returns the roots. In the first case, return a list of 2 numbers: [2, 3]. In the second case, return a list of only one number: [2]. In the third case, return an empty list: [].


In [6]:
import math
def solve_quadratic_equation(a, b, c):
    """
    Returns the real solutions of the quadratic equation ax^2 + bx + c = 0
    """
    # Check if we have linear equation, a = 0
    if a == 0:
        return [ -(c/b)]
    # determine the value of b**2 - 4ac
    d = float(b * b - 4.0 * a * c)
    if d < 0: # there is no roots
        return [] # return empty array
    else: #  we have some roots
        if d == 0: # only one root
            return [ (-b)/(2.0*a) ] 
        else: # or two roots
            return [(-b - (math.sqrt(d)))/(2.0*a), (-b + (math.sqrt(d)))/(2.0*a)]

In [7]:
# Testing: Execute this cell. The outputs should match the expected outputs. Feel free to write more tests
print(solve_quadratic_equation(1, -2, -1.25)) # [-0.5, 2.5] <== this comment is not correct for given parametters
print(solve_quadratic_equation(1, -8, 16)) # [4.0]
print(solve_quadratic_equation(1, 1, 1)) # []
print(solve_quadratic_equation(0, 2, 5))


[-0.5, 2.5]
[4.0]
[]
[-2.5]

Bonus: Last time we saw how to solve a linear equation. Remember that linear equations are just like quadratic equations with $a = 0$. In this case, however, division by 0 will throw an error. Extend your function above to support solving linear equations (in the same way we did it last time).

Problem 4. Equation of a Line

Let's go back to our linear equations and systems. There are many ways to define what "linear" means, but they all boil down to the same thing.

The equation $ax + b = 0$ is called linear because the function $f(x) = ax+b$ is a linear function. We know that there are several ways to know what one particular function means. One of them is to just write the expression for it, as we did above. Another way is to plot it. This is one of the most exciting parts of maths and science - when we have to fiddle around with beautiful plots (although not so beautiful in this case).

The function produces a straight line and we can see it.

How do we plot functions in general? Ww know that functions take many (possibly infinitely many) inputs. We can't draw all of them. We could, however, evaluate the function at some points and connect them with tiny straight lines. If the points are too many, we won't notice - the plot will look smooth.

Now, let's take a function, e.g. $y = 2x + 3$ and plot it. For this, we're going to use numpy arrays. This is a special type of array which has two characteristics:

  • All elements in it must be of the same type
  • All operations are broadcast: if x = [1, 2, 3, 10] and we write 2 * x, we'll get [2, 4, 6, 20]. That is, all operations are performed at all indices. This is very powerful, easy to use and saves us A LOT of looping.

There's one more thing: it's blazingly fast because all computations are done in C, instead of Python.

First let's import numpy. Since the name is a bit long, a common convention is to give it an alias:

import numpy as np

Import that at the top cell and don't forget to re-run it.

Next, let's create a range of values, e.g. $[-3, 5]$. There are two ways to do this. np.arange(start, stop, step) will give us evenly spaced numbers with a given step, while np.linspace(start, stop, num) will give us num samples. You see, one uses a fixed step, the other uses a number of points to return. When plotting functions, we usually use the latter. Let's generate, say, 1000 points (we know a straight line only needs two but we're generalizing the concept of plotting here :)).

x = np.linspace(-3, 5, 1000)

Now, let's generate our function variable

y = 2 * x + 3

We can print the values if we like but we're more interested in plotting them. To do this, first let's import a plotting library. matplotlib is the most commnly used one and we usually give it an alias as well.

import matplotlib.pyplot as plt

Now, let's plot the values. To do this, we just call the plot() function. Notice that the top-most part of this notebook contains a "magic string": %matplotlib inline. This hints Jupyter to display all plots inside the notebook. However, it's a good practice to call show() after our plot is ready.

plt.plot(x, y)
plt.show()

In [8]:
# Write your code here
x = np.linspace(-3, 5, 1000)
y = 2 * x + 3
plt.plot(x,y)
plt.show()


It doesn't look too bad bit we can do much better. See how the axes don't look like they should? Let's move them to zeto. This can be done using the "spines" of the plot (i.e. the borders).

All matplotlib figures can have many plots (subfigures) inside them. That's why when performing an operation, we have to specify a target figure. There is a default one and we can get it by using plt.gca(). We usually call it ax for "axis". Let's save it in a variable (in order to prevent multiple calculations and to make code prettier). Let's now move the bottom and left spines to the origin $(0, 0)$ and hide the top and right one.

ax = plt.gca()
ax.spines["bottom"].set_position("zero")
ax.spines["left"].set_position("zero")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)

Note: All plot manipulations HAVE TO be done before calling show(). It's up to you whether they should be before or after the function you're plotting.

This should look better now. We can, of course, do much better (e.g. remove the double 0 at the origin and replace it with a single one), but this is left as an exercise for the reader :).


In [9]:
# Copy and edit your code here
x = np.linspace(-3, 5, 1000)
y = 2 * x + 3
ax = plt.gca()
ax.spines["bottom"].set_position("zero")
ax.spines["left"].set_position("zero")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
yticks = ax.yaxis.get_major_ticks()
yticks[2].label1.set_visible(False)
plt.plot(x, y)
plt.show()


Problem 5. Linearizing Functions

Why is the line equation so useful? The main reason is because it's so easy to work with. Scientists actually try their best to linearize functions, that is, to make linear functions from non-linear ones. There are several ways of doing this. One of them involves derivatives and we'll talk about it later in the course.

A commonly used method for linearizing functions is through algebraic transformations. Try to linearize $$ y = ae^{bx} $$

Hint: The inverse operation of $e^{x}$ is $\ln(x)$. Start by taking $\ln$ of both sides and see what you can do. Your goal is to transform the function into another, linear function. You can look up more hints on the Internet :).

Write your result here.

$$ \ln(y) = \ln(a) + bx $$

Problem 6. Generalizing the Plotting Function

Let's now use the power of Python to generalize the code we created to plot. In Python, you can pass functions as parameters to other functions. We'll utilize this to pass the math function that we're going to plot.

Note: We can also pass lambda expressions (anonymous functions) like this:

lambda x: x + 2

This is a shorter way to write

def some_anonymous_function(x):
    return x + 2

We'll also need a range of x values. We may also provide other optional parameters which will help set up our plot. These may include titles, legends, colors, fonts, etc. Let's stick to the basics now.

Write a Python function which takes another function, x range and number of points, and plots the function graph by evaluating it at every point.

BIG hint: If you want to use not only numpy functions for f but any one function, a very useful (and easy) thing to do, is to vectorize the function f (e.g. to allow it to be used with numpy broadcasting):

f_vectorized = np.vectorize(f)
y = f_vectorized(x)

In [10]:
def plot_math_function(f, min_x, max_x, num_points):
    # Write your code here
    x = np.linspace(min_x, max_x, num_points) # transfer min_x, max_x and the count of points
    f_vectorized = np.vectorize(f) # transform the given function f to vector for numpy caclulations
    
    y = f_vectorized(x) # use vectorised function to calculate all coresponging Ys
    fig, ax = plt.subplots() # set alias for pmatplotlib.pyplot.gca template for ploting
    # manipulate the plot object properties
    ax.spines["bottom"].set_position("zero") # insert the axis to zero
    ax.spines["left"].set_position("zero")
    ax.spines["top"].set_visible(False) # hide top frame
    ax.spines["right"].set_visible(False)
    plt.plot(x, y) # plot here, to populate ticks objects
    ax.set_yticklabels(ax.get_yticks()) # extract ticks and set them to plt.subplots (ax)
    ax.set_xticklabels(ax.get_xticks())
    ylabels = ax.get_yticklabels() # create list of ticks labels
    xlabels = ax.get_xticklabels() 
    # create variables for indexes from labels list where we are going to hide the labels 
    xzero = 0
    yzero = 0
    # loop trough labels and extract index of text '0.0'
    for num, xlabel in enumerate(xlabels, start=0):
        if xlabel.get_text() == '0.0':
            xzero = num
    for num, ylabel in enumerate(ylabels, start=0):
        if ylabel.get_text() == '0.0':
            yzero = num
    yticks = ax.yaxis.get_major_ticks() # take all Y major ticks
    xticks = ax.xaxis.get_major_ticks()
    ax.set_xlabel('x') # we can have set_xlabel('name', fontsize = 12)
    ax.set_ylabel('y').set_rotation(0)
    # offset label for zero of X
    xticks[xzero].label1.set_horizontalalignment('right')
    # hide label1 on index with zero of Y
    yticks[yzero].label1.set_visible(False)
    ax.xaxis.set_label_coords(1.05, yzero / len(ylabels)) # fine positioning of x label
    ax.yaxis.set_label_coords((1 * abs(min_x))/((max_x) - (min_x)), 1.02) # possitioning of Y label
    plt.show()

In [11]:
plot_math_function(lambda x: 2 * x + 3, -3, 5, 1000)
plot_math_function(lambda x: -x + 8, -1, 10, 1000)
plot_math_function(lambda x: x**2 - x - 2, -3, 4, 1000)
plot_math_function(lambda x: np.sin(x), -np.pi, np.pi, 1000)
plot_math_function(lambda x: np.sin(x) / x, -4 * np.pi, 4 * np.pi, 1000)


Problem 7. Solving Equations Graphically

Now that we have a general plotting function, we can use it for more interesting things. Sometimes we don't need to know what the exact solution is, just to see where it lies. We can do this by plotting the two functions around the "=" sign ans seeing where they intersect. Take, for example, the equation $2x + 3 = 0$. The two functions are $f(x) = 2x + 3$ and $g(x) = 0$. Since they should be equal, the point of their intersection is the solution of the given equation. We don't need to bother marking the point of intersection right now, just showing the functions.

To do this, we'll need to improve our plotting function yet once. This time we'll need to take multiple functions and plot them all on the same graph. Note that we still need to provide the $[x_{min}; x_{max}]$ range and it's going to be the same for all functions.

vectorized_fs = [np.vectorize(f) for f in functions]
ys = [vectorized_f(x) for vectorized_f in vectorized_fs]

In [12]:
def plot_math_functions(functions, min_x, max_x, num_points):
    # Write your code here
    x = np.linspace(min_x, max_x, num_points)
    vectorized_fs = [np.vectorize(f) for f in functions]
    ys = [vectorized_f(x) for vectorized_f in vectorized_fs]
    fig, ax = plt.subplots() # set alias for pmatplotlib.pyplot.gca template for ploting
    # manipulate the plot object properties
    ax.spines["bottom"].set_position("zero") # insert the axis to zero
    ax.spines["left"].set_position("zero")
    ax.spines["top"].set_visible(False) # hide top frame
    ax.spines["right"].set_visible(False)
    for y in ys:
        plt.plot(x, y)
    ax.set_yticklabels(ax.get_yticks()) # extract ticks and set them to plt.subplots (ax)
    ax.set_xticklabels(ax.get_xticks())
    ylabels = ax.get_yticklabels() # create list of ticks labels
    xlabels = ax.get_xticklabels() 
    # create variables for indexes from labels list where we are going to hide the labels 
    xzero = 0
    yzero = 0
    # loop trough labels and extract index of text '0.0'
    for num, xlabel in enumerate(xlabels, start=0):
        if xlabel.get_text() == '0.0':
            xzero = num
    for num, ylabel in enumerate(ylabels, start=0):
        if ylabel.get_text() == '0.0':
            yzero = num
    yticks = ax.yaxis.get_major_ticks() # take all Y major ticks
    xticks = ax.xaxis.get_major_ticks()
    ax.set_xlabel('x') # we can have set_xlabel('name', fontsize = 12)
    ax.set_ylabel('y').set_rotation(0)
    # offset label for zero of X
    xticks[xzero].label1.set_horizontalalignment('right')
    # hide label1 on index with zero of Y
    yticks[yzero].label1.set_visible(False)
    ax.xaxis.set_label_coords(1.02, yzero / len(ylabels)) # fine positioning of x label
    ax.yaxis.set_label_coords((1 * abs(min_x))/((max_x) - (min_x)), 1.05) # possitioning of Y label
    plt.show()

In [13]:
plot_math_functions([lambda x: 2 * x + 3, lambda x: 0], -3, 5, 1000)
plot_math_functions([lambda x: 3 * x**2 - 2 * x + 5, lambda x: 3 * x + 7], -2, 3, 1000)


This is also a way to plot the solutions of systems of equation, like the one we solved last time. Let's actually try it.


In [14]:
plot_math_functions([lambda x: (-4 * x + 7) / 3, lambda x: (-3 * x + 8) / 5, lambda x: (-x - 1) / -2], -1, 4, 1000)


Problem 8. Trigonometric Functions

We already saw the graph of the function $y = \sin(x)$. But, how do we define the trigonometric functions once again? Let's quickly review that.

The two basic trigonometric functions are defined as the ratio of two sides: $$ \sin(x) = \frac{\text{opposite}}{\text{hypotenuse}} $$ $$ \cos(x) = \frac{\text{adjacent}}{\text{hypotenuse}} $$

And also: $$ \tan(x) = \frac{\text{opposite}}{\text{adjacent}} = \frac{\sin(x)}{\cos(x)} $$ $$ \cot(x) = \frac{\text{adjacent}}{\text{opposite}} = \frac{\cos(x)}{\sin(x)} $$

This is fine, but using this, "right-triangle" definition, we're able to calculate the trigonometric functions of angles up to $90^\circ$. But we can do better. Let's now imagine a circle centered at the origin of the coordinate system, with radius $r = 1$. This is called a "unit circle".

We can now see exactly the same picture. The $x$-coordinate of the point in the circle corresponds to $\cos(\alpha)$ and the $y$-coordinate - to $\sin(\alpha)$. What did we get? We're now able to define the trigonometric functions for all degrees up to $360^\circ$. After that, the same values repeat: these functions are periodic: $$ \sin(k.360^\circ + \alpha) = \sin(\alpha), k = 0, 1, 2, \dots $$ $$ \cos(k.360^\circ + \alpha) = \cos(\alpha), k = 0, 1, 2, \dots $$

We can, of course, use this picture to derive other identities, such as: $$ \sin(90^\circ + \alpha) = \cos(\alpha) $$

A very important property of the sine and cosine is that they accept values in the range $(-\infty; \infty)$ and produce values in the range $[-1; 1]$. The two other functions take values in the range $(-\infty; \infty)$ except when their denominators are zero and produce values in the same range.

Radians

A degree is a geometric object, $1/360$th of a full circle. This is quite inconvenient when we work with angles. There is another, natural and intrinsic measure of angles. It's called the radian and can be written as $\text{rad}$ or without any designation, so $\sin(2)$ means "sine of two radians".

It's defined as the central angle of an arc with length equal to the circle's radius and $1\text{rad} \approx 57.296^\circ$.

We know that the circle circumference is $C = 2\pi r$, therefore we can fit exactly $2\pi$ arcs with length $r$ in $C$. The angle corresponding to this is $360^\circ$ or $2\pi\ \text{rad}$. Also, $\pi rad = 180^\circ$.

(Some people prefer using $\tau = 2\pi$ to avoid confusion with always multiplying by 2 or 0.5 but we'll use the standard notation here.)

NOTE: All trigonometric functions in math and numpy accept radians as arguments. In order to convert between radians and degrees, you can use the relations $\text{[deg]} = 180/\pi.\text{[rad]}, \text{[rad]} = \pi/180.\text{[deg]}$. This can be done using np.deg2rad() and np.rad2deg() respectively.

Inverse trigonometric functions

All trigonometric functions have their inverses. If you plug in, say $\pi/4$ in the $\sin(x)$ function, you get $\sqrt{2}/2$. The inverse functions (also called, arc-functions) take arguments in the interval $[-1; 1]$ and return the angle that they correspond to. Take arcsine for example: $$ \arcsin(y) = x: sin(y) = x $$ $$ \arcsin\left(\frac{\sqrt{2}}{2}\right) = \frac{\pi}{4} $$

Please note that this is NOT entirely correct. From the relations we found: $$\sin(x) = sin(2k\pi + x), k = 0, 1, 2, \dots $$

it follows that $\arcsin(x)$ has infinitely many values, separated by $2k\pi$ radians each: $$ \arcsin\left(\frac{\sqrt{2}}{2}\right) = \frac{\pi}{4} + 2k\pi, k = 0, 1, 2, \dots $$

In most cases, however, we're interested in the first value (when $k = 0$). It's called the principal value.

Note 1: There are inverse functions for all four basic trigonometric functions: $\arcsin$, $\arccos$, $\arctan$, $\text{arccot}$. These are sometimes written as $\sin^{-1}(x)$, $cos^{-1}(x)$, etc. These definitions are completely equivalent.

Just notice the difference between $\sin^{-1}(x) := \arcsin(x)$ and $\sin(x^{-1}) = \sin(1/x)$.

Exercise

Use the plotting function you wrote above to plot the inverse trigonometric functions.


In [15]:
plot_math_functions([lambda x: np.arcsin(x), lambda x: np.arccos(x), lambda x: np.arctan(x), lambda x: np.arctan(1/x)], -1.0, 1.0, 1000)


Problem 9. Equation of a Circle

The best news for anyone who hates geometry at school (but loves algebra) is that all geometric figures (lines, circles, ellipses, vectors, etc.) that we learn at school can be expressed via algebraic equations. We already saw the equation of a line. Now that we have a formal definition of trigonometric functions and we know what the unit circle is, we can derive the equation of a circle. Note that this is not so easy.

First, let's remember the definition of a circle: all points $(x; y)$ that are at a specific distance $r$ from a central point $O (h; k)$.

Let's look at the triangle whose hypotenuse is $r$. From the Pytagorean theorem, we have: $$ (x - h)^2 + (y - k)^2 = r^2 $$

This is the equation of the circle. Note that it cannot be written as $y = f(x)$ unambiguously. This is because for a given value of $x$, there are two values of $y$.

In a more concrete case, a circle centered on the origin of the xy-plane, has an equation: $$ x^2 + y^2 = r^2 $$

If the radius $r = 1$, the equation becomes yet simpler: $$ x^2 + y^2 = 1 $$

This is the equation of the unit circle.

Note that this equation gives us points which lie only on the circumference of the circle. If we want to take points inside the circle, we need to use an inequality instead: $$ (x - h)^2 + (y - k)^2 \le r^2 $$

Note how this includes not only points which lie "a radius away" from the center, but also closer ones (this is what the $\le$ sign means).

Exercise

Plot a circle given its equation. Note that since this is not a strict mathematical function, we'll most likely have trouble plotting that.

In order to plot these, so-called, implicit functions, we'll need to take another approach. Instead of generating evenly spaced $x$s and generating $y$s using our function, we'll now generate all possible $(x; y)$ pairs and take only the ones we need.

First, let's generate each dimension vector on its own. After that, let's do what's called a "Cartesian product" of those values: this will generate a 2D matrix of coordinates. We can do this using np.meshgrid(x, y). Have a look at the example:

xy = np.meshgrid([1, 2, 3], [2, 3, 4])
# Output:
[
    array([[1, 2, 3],
        [1, 2, 3],
        [1, 2, 3]]),
    array([[2, 2, 2],
        [3, 3, 3],
        [4, 4, 4]])
]

Later, matplotlib will use the coordinate mesh to plot our values. After that, we need to specify the circle equation:

circle = x ** 2 + y ** 2 - r ** 2

Finally, we need to plot that:

plt.contour(x, y, circle, [0])

Note: matplotlib usually creates unequal axes (1 unit in $x$ does not equal 1 unit in $y$), that's why your circle will appear as an ellipse. To fix this, you need to set an equal aspect ratio. Look it up :).

Wrap your code in a function.


In [215]:
def plot_circle(x_c, y_c, r):
    """
    Plots the circle with center C(x_c; y_c) and radius r.
    This corresponds to plotting the equation x^2 + y^2 = r^2
    """
    # Write your code here
    plt.gca().set_aspect('equal')
    y = np.linspace(y_c -r - 1, y_c + r + 1, 30) 
    x = np.linspace(x_c - r - 1, x_c + r + 1, 30)
    x, y = np.meshgrid(x, y)
    circle = x ** 2 + y ** 2 - r ** 2
    plt.contour(x, y, circle, [0])
    plt.show()

In [216]:
plot_circle(0, 0, 2)


* Problem 10. Perlin Noise

This algorithm has many applications in computer graphics and can serve to demonstrate several things... and help us learn about math, algorithms and Python :).

Noise

Noise is just random values. We can generate noise by just calling a random generator. Note that these are actually called pseudorandom generators. We'll talk about this later in this course. We can generate noise in however many dimensions we want. For example, if we want to generate a single dimension, we just pick N random values and call it a day. If we want to generate a 2D noise space, we can take an approach which is similar to what we already did with np.meshgrid().

$$ \text{noise}(x, y) = N, N \in [n_{min}, n_{max}] $$

This function takes two coordinates and returns a single number N between $n_{min}$ and $n_{max}$. (This is what we call a "scalar field").

Random variables are always connected to distributions. We'll talk about these a great deal but now let's just say that these define what our noise will look like. In the most basic case, we can have "uniform noise" - that is, each point in our little noise space $[n_{min}, n_{max}]$ will have an equal chance (probability) of being selected.

Perlin noise

There are many more distributions but right now we'll want to have a look at a particular one. Perlin noise is a kind of noise which looks smooth. It looks cool, especially if it's colored. The output may be tweaked to look like clouds, fire, etc. 3D Perlin noise is most widely used to generate random terrain.

Algorithm

... Now you're on your own :). Research how the algorithm is implemented (note that this will require that you understand some other basic concepts like vectors and gradients).

Your task

  1. Research about the problem. See what articles, papers, Python notebooks, demos, etc. other people have created
  2. Create a new notebook and document your findings. Include any assumptions, models, formulas, etc. that you're using
  3. Implement the algorithm. Try not to copy others' work, rather try to do it on your own using the model you've created
  4. Test and improve the algorithm
  5. (Optional) Create a cool demo :), e.g. using Perlin noise to simulate clouds. You can even do an animation (hint: you'll need gradients not only in space but also in time)
  6. Communicate the results (e.g. in the Softuni forum)

Hint: This is a very good resource. It can show you both how to organize your notebook (which is important) and how to implement the algorithm.